﻿using System.ComponentModel;
using System;
using System.Windows.Forms;
using Raider.WinForms.Actions.Linkage;
using Raider.WinForms.Actions.Utils;
using System.Threading;

namespace Raider.WinForms.Actions
{
    /// <summary>
    /// Represents the action, which may be executed or updated asynchronously.
    /// </summary>
    /// <remarks>
    /// List of supported client controls and components:
    /// <list type="bullet">
    /// <item><see cref="Control"/></item>
    /// <item><see cref="ToolStripItem"/></item>
    /// <item><see cref="MenuItem"/></item>
    /// </list>
    /// </remarks>
    public class AsyncAction : BaseAction
    {
        private readonly object syncRoot = new object();

        private bool asyncExecute;
        private bool asyncUpdate;

        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncAction"/>.
        /// </summary>
        /// <param name="title">Default title for the action (empty by default).</param>
        public AsyncAction(string title = "")
            : base(title)
        {
        }

        /// <summary>
        /// Gets or sets the value, indicating whether the <see cref="IAction.Execute"/> event
        /// should be invoked asynchronously.
        /// </summary>
        [DefaultValue(false), Bindable(false)]
        [Category("Behavior"), Description("Defines whether the action is executed asynchronously.")]
        public virtual bool AsyncExecute
        {
            get { return asyncExecute; }
            set { if (asyncExecute != value) { asyncExecute = value; OnPropertyChanged("AsyncExecute"); } }
        }

        /// <summary>
        /// Gets or sets the value, indicating whether the <see cref="IAction.Update"/> event
        /// should be invoked asynchronously.
        /// </summary>
        [DefaultValue(false), Bindable(false)]
        [Category("Behavior"), Description("Defines whether the action is updated asynchronously.")]
        public virtual bool AsyncUpdate
        {
            get { return asyncUpdate; }
            set { if (asyncUpdate != value) { asyncUpdate = value; OnPropertyChanged("AsyncUpdate"); } }
        }

        /// <summary>
        /// Explicitly executes the action and returns the result.
        /// </summary>
        /// <param name="argument">Optional argument, that will be passed 
        /// to <see cref="ExecuteEventArgs"/>.</param>
        /// <remarks>
        /// Note that action will be executed regardless of <see cref="IAction.Enabled"/> property value.
        /// </remarks>
        /// <returns>User-defined result of the execution. If action is executed asynchronously, method
        /// will return the <see cref="IAsyncResult"/> instance.</returns>
        public sealed override object DoExecute(object argument = null)
        {
            if (AsyncExecute)
            {            
                var cancelEventArgs = new ExecutingEventArgs(false, argument);
                OnExecuting(cancelEventArgs);

                if (cancelEventArgs.Cancel)
                    return null;

                var executeArgs = cancelEventArgs.GetExecuteArgs();
                var result = ThreadUtils.ExecuteAsync(
                    args => { lock (syncRoot) { OnExecute(args); } },
                    args => { lock (syncRoot) { OnExecuted(args.GetExecutedArgs()); } },
                    null, executeArgs);

                return result;
            }
            else
            {
                return base.DoExecute(argument);
            }
        }

        /// <summary>
        /// Explicitly updates the action.
        /// </summary>
        /// <param name="argument">Optional argument of any type, that will be passed
        /// to <see cref="UpdateEventArgs"/>.</param>
        /// <remarks>
        /// Note that action will be updated regardless of the <see cref="Enabled"/> property value.
        /// </remarks>
        public sealed override void DoUpdate(object argument)
        {
            if (AsyncUpdate)
            {            
                var cancelEventArgs = new CancelEventArgs();
                OnUpdating(cancelEventArgs);

                if (cancelEventArgs.Cancel)
                    return;

                var updateArgs = argument == null ? UpdateEventArgs.Empty : new UpdateEventArgs(argument);
                ThreadUtils.ExecuteAsync(
                    args => { lock (syncRoot) { OnUpdate(args); } },
                    args => { lock (syncRoot) { OnUpdated(EventArgs.Empty); } },
                    null, updateArgs);
            }
            else
            {
                base.DoUpdate(argument);
            }
        }
    }
}
